#include "tcp_server.h"

#include <event2/buffer.h>
#include <event2/bufferevent.h>
#include <event2/listener.h>
#include <signal.h>
#include <string.h>

#if defined(__GNUC__)
    #include <arpa/inet.h>
    #include <sys/socket.h>
#elif defined(_MSC_VER)
    #if defined(_WIN32)
        #ifndef WIN32_LEAN_AND_MEAN
            #define WIN32_LEAN_AND_MEAN
        #endif //! WIN32_LEAN_AND_MEAN
        #include <Windows.h>
        #include <Winsock2.h>
        #include <process.h>
        #pragma comment(lib, "Ws2_32.lib")
        #undef WIN32_LEAN_AND_MEAN
    #endif //!_WIN32
#endif

TcpServer::TcpServer()
{
#if defined(_MSC_VER)
    WSADATA wsaData;
    WSAStartup(MAKEWORD(2, 2), &wsaData);
#endif
}

TcpServer::~TcpServer()
{
#if defined(_MSC_VER)
    WSACleanup();
#endif
}

void TcpServer::start_server(int port)
{
    struct sockaddr_in sock_addr = {0};
    sock_addr.sin_family         = AF_INET;
    sock_addr.sin_addr.s_addr    = htonl(INADDR_ANY);
    sock_addr.sin_port           = htons(port);

    struct event_base     *base     = event_base_new();
    struct evconnlistener *listener = evconnlistener_new_bind(
        base, listener_cb, base, LEV_OPT_REUSEABLE | LEV_OPT_CLOSE_ON_FREE, -1, (struct sockaddr *)&sock_addr, sizeof(sock_addr));
    if (!listener) {
        printf("evconnlistener_new_bind error\n");
        return;
    }

    struct event *signal_event = evsignal_new(base, SIGINT, signal_cb, (void *)base);
    if (!signal_event || event_add(signal_event, nullptr) < 0) {
        printf("Could not create/add a signal event!\n");
        return;
    }

    event_base_dispatch(base);

    evconnlistener_free(listener);
    event_free(signal_event);
    event_base_free(base);
}

void TcpServer::listener_cb(struct evconnlistener *listener, evutil_socket_t fd, struct sockaddr *sa, int socklen, void *arg)
{
    struct event_base  *base = (event_base *)arg;
    struct bufferevent *bev;

    bev = bufferevent_socket_new(base, fd, BEV_OPT_CLOSE_ON_FREE);
    if (!bev) {
        fprintf(stderr, "Error constructing bufferevent!");
        event_base_loopbreak(base);
        return;
    }
    bufferevent_setcb(bev, conn_read_cb, conn_write_cb, conn_event_cb, nullptr);
    bufferevent_enable(bev, EV_WRITE);
    bufferevent_enable(bev, EV_READ);

    static const char MESSAGE[] = "Hello, World!\n";
    bufferevent_write(bev, MESSAGE, strlen(MESSAGE));
}

void TcpServer::conn_read_cb(struct bufferevent *bev, void *arg)
{
    struct evbuffer *input  = bufferevent_get_input(bev);
    struct evbuffer *output = bufferevent_get_output(bev);
    size_t           len    = evbuffer_get_length(input);
    evbuffer_add_buffer(output, input);
}

void TcpServer::conn_write_cb(struct bufferevent *bev, void *arg)
{
    struct evbuffer *output = bufferevent_get_output(bev);
    if (evbuffer_get_length(output) == 0) {
        bufferevent_free(bev);
    }
}

void TcpServer::conn_event_cb(struct bufferevent *bev, short events, void *arg)
{
    if (events & BEV_EVENT_EOF) {
        printf("Connection closed\n");
    } else if (events & BEV_EVENT_ERROR) {
        printf("%s\n", evutil_socket_error_to_string(EVUTIL_SOCKET_ERROR()));
    }
    bufferevent_free(bev);
}

void TcpServer::signal_cb(evutil_socket_t sig, short events, void *arg)
{
    struct event_base *base  = (event_base *)arg;
    struct timeval     delay = {2, 0};

    printf("Caught an interrupt signal; exiting cleanly in two seconds.\n");

    event_base_loopexit(base, &delay);
}